'use strict'

import { WebDao, ApiDao } from '@/common/dao';


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 


const DEFAULT_PAGE_SIZE = 10;
const DEFAULT_PAGE_NUMBER = 1;


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 


export function removeInvalidCondition(object={}) {
	let temp = {};

	for (let key in object) {
		let value = object[key];

		if (null === value || undefined === value || "" === value) {
			console.debug(`[condition] remove ${key}: ${value}`);
		} else {
			temp[key] = value;
		}
	}

	return temp;
}


export function toSize(byteSize) {
	const flag = ["B", "KB", "MB", "GB", "TB", "PB"];

	// 1GB = 1L << 30
    // x / 1024 <=> x >> 10
    let count = 0;
    let temp = byteSize * 10;

    while ((temp >> 10) >= 10) {
        count++;

        temp = temp >> 10;
    }


    let s = temp / 10;
    let l = temp % 10;

    if (count > 2 && 0 != l) {
        return s + "." + l + flag[count];
    }
    return s + flag[count];
}


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 


function contactPns(pageNumber, pageSize) {
	return (pageNumber ?? "1") + "-" + (pageSize ?? DEFAULT_PAGE_SIZE);
}

function parameterizePns(pageNumber, pageSize) {
	return "pageNumber=" + pageNumber
			+ (pageSize ? "&pageSize=" + pageSize : '');
}




const routerConfig = {
	mode: "dev",
	version: "",
	page: {
		status: (contestId, number, size) => {
			return "/status?" + parameterizePns(number, size);
		},
		problems: (number, size) => {
			return "/problems" + parameterizePns(number, size);
		},
		problem: id => `/problem/${id}`,
		contests: (number, size) => {
			return "contests.html?" + parameterizePns(number, size);
		},
		contest: {
			index: (id) => {
				return `/contest/${id}`;
			},
			problem: (contestId, identifier) => {
				return `/contest/${contestId}/problem/${identifier}`;
			},
			status: (contestId, number, size) => {
				return `/contest/${contestId}/status?`
						+ parameterizePns(number, size);
			},
		},
		rank: (pageNumber, pageSize) => {
			return "/rank?" + parameterizePns(pageNumber, pageSize);
		},
		user: {
			login: "/login.html",
			judgeInfo: (userId) => {
				return `/user/${userId}/info`;
			},
		},
		solution: (id, contestId) => {
			if (contestId) {
				return `/contest/${contestId}/solution/${id}`;
			}
			return `/solution/${id}`;
		},
		groups: (number, size) => {
			return "/groups?" + parameterizePns(number, size);
		},
		group: {
			index: "/group",
			join: (id, name) => {
				return "/group/join"
						+ "?id=" + groupId
						+ "&name=" + groupName;
			}
		}
	},
	api: {
		dev: {
			find: "find",
			update: "update",
			judge: {
				status: (base, path, runId) => `${base}${path}?runId=${runId}`,
			},
			test: (base, path, userId) => `${base}/user/${userId}/profile`,
			problem: {
				judge: "/judge",
			},
			contest: {
				problem: {
					judge: "/judge",
				},
			},
			paste: {
				add: "add",
			},
			user: {
				regist: "/register",
				login: "/login",
				logout: "/logout",
				loginCaptcha: (base, path, timestamp) => 
					`${base}/login/captcha?t=${timestamp}`,
				registCaptcha: (base, path, timestamp) => 
					`${base}/register/captcha?t=${timestamp}`,
				resetPasswd: "passwd/reset",
				resetPasswdCaptcha: (base, path, timestamp=((new Date()).getTime())) => 
					`${base}/user/reset-passwd/send-vcode?t=${timestamp}`,
				changePasswd: "passwd/change",

				email: {
					bind: "/user/email/bind",
					change: "/user/email/change",
					bindCaptcha: (base, path) => `${base}/user/email/bind/send-mail`,
					unbind: "/user/email/unbind",
					unbindCaptcha: (base, path) => `${base}/user/email/unbind/send-mail`,
				},

				profile: "profile",
				update: "update",
				isLogin: "/isLogin",
				judgeInfo: (base, path, id) => `${base}${path}?id=${id}`,
			}
		},
		prod: {

		},
	},
};

function buildRouter(config) {
	const modeList = ["dev", "prod", "test"];
	const mode = modeList.includes(config.mode) ? config.mode : "dev";
	const version = config.version;
	const apiPath = version ? `/api/${version}` : `/api`;

	const api = {};
	const page = config.page;

	const func = (route, data, target) => {
		for (let key in data) {
			let value = data[key];
			let type = typeof(value);

			if ('string' === type) {
				if (value.startsWith("//")) {
					target[key] = value;
				} if (value.startsWith("/")) {
					target[key] = apiPath + value;
				} else {
					target[key] = `${route}/${value}`;
				}
			} else if ('function' === type) {
				target[key] = (...args) => {
					let path = `${route}/${key}`.replace(apiPath, '');
					args = [apiPath, path].concat(args);

					return value.apply(this, args);
				};
			} else if ('object' === type) {
				func(`${route}/${key}`, value, target[key] = {});
			} else {
				console.warn("unsupport type: ", type);
			}
		}
	}

	func(apiPath, config.api[mode], api);

	return {
		mode: mode,
		env: {
			dev: mode === "dev",
			prod: mode === "prod",
			test: mode === "test",
		},
		version: version,
		page: page,
		api: api,
	};
}

export const Router = Object.freeze(buildRouter(routerConfig));




// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 





// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 



export class ConfigService {

	static getConfig() {
		return WebDao.getJson("/mock/config.json");
	}



	static #getLanguagesUrl() {
		return "/support/lang/run.json";
	}

	static getLanguages() {
		return WebDao.getJson(this.#getLanguagesUrl());
	}

	static getLanguageAndProcess() {
		return this.getLanguages().then(result => {
			let langMapping = {};

			for (let item of result) {
				langMapping[item.id] = item.lang;
			}

			return Object.freeze(langMapping);
		});
	}
}



// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 


export class StatusService {
	static resultMapping = Object.freeze({
		"4" : { "name": "accepted", 			"class": "c-green"	},
		"0" : { "name": "pending", 				"class": "c-black"	},
		"1" : { "name": "rejudging", 			"class": "c-black"	},
		"2" : { "name": "compiling", 			"class": "c-black"	},
		"3" : { "name": "running", 				"class": "c-black"	},
		"5" : { "name": "presentation-error", 	"class": "c-black"	},
		"6" : { "name": "wrong-answer", 		"class": "c-red"	},
		"7" : { "name": "time-limit-exceed", 	"class": "c-red"	},
		"8" : { "name": "memory-limit-exceed", 	"class": "c-red"	},
		"9" : { "name": "output-limit-exceed", 	"class": "c-red"	},
		"10": { "name": "runtime-error", 		"class": "c-red"	},
		"11": { "name": "compile-error", 		"class": "c-red"	},
		"12": { "name": "permission-denied", 	"class": "c-red"	},
		"404": { "name": "unknow-mistake", 		"class": "c-red"	},
	});


	static processes(items) {
		return Object.freeze(this.processesWithoutFreeze(items));
	}

	static processesWithoutFreeze(items) {
		for (let item of items) {
			this.processWithoutFreeze(item);
		}
		return items;
	}

	static process(item, attr="result") {
		return Object.freeze(this.processWithoutFreeze(item, attr));
	}

	static processWithoutFreeze(item, attr="result") {
		let result = this.resultMapping[item[attr]];

		if (result) {
			item[attr + "Name"] = result.name;
			item[attr + "Class"] = result.class;
		}


		// memoryUsed: KB, codeLength: B
		item.timeUsed = `${item.timeUsed}ms`;
		item.memoryUsed = toSize(item.memoryUsed * 1024);
		item.codeLength = toSize(item.codeLength);
		return item;
	}


	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


	static getPage(contestId, pageNumber, pageSize, conditions) {
		let obj = {};

		Object.assign(obj, removeInvalidCondition(conditions));

		return ApiDao.findPage({
			table: "view_solution",
			conditions: obj,
			page: {
				number: 1,
				size: pageSize,
			},
		});
	}


	static get(runId) {
		return ApiDao.getJson(Router.api.judge.status(runId));
	}
}



// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 



export class ProblemService {

	static processes(problems) {
		return Object.freeze(this.processesWithoutFreeze(problems));
	}

	static processesWithoutFreeze(problems) {
		for (let problem of problems) {
			this.processWithoutFreeze(problem);
		}
		return problems;
	}


	static process(problem) {
		return Object.freeze(this.processWithoutFreeze(problem));
	}

	static processWithoutFreeze(problem) {
		this.processIdentifier(problem);
		this.processFinalTitle(problem);
		this.processTimeLimit(problem);

		return problem;
	}

	static processIdentifier(problem) {
		if (problem.hasOwnProperty("identifier")) {
			const identifier = problem.identifier;

			if ('number' === typeof(identifier)) {
				// identifier从0开始： A => 65, identifier => [0, 26)
				// 否则：64 + identifier
				const temp = 64 + identifier;
				problem.identifierString = String.fromCharCode(temp);
			}
		}
	}

	static processFinalTitle(problem) {
		problem.finalTitle = (problem.identifierString ?? problem.id)
				+ ": " + problem.name;
	}

	static processTimeLimit(problem, parsetoArray=true) {
		const limits = problem.limits;

		if (!limits || !limits.length) return;

		let timeLimit = new Map();
		let memoryLimit = new Map();

		for (let i = 0; i < limits.length; i++) {
			let item = limits[i];

			let languageName = item.languageName || item.language;
			let time = item.timeLimit;
			let memory = item.memoryLimit;


			let timeLimits = timeLimit.get(time);
			let memoryLimits = memoryLimit.get(memory);

			if (timeLimits) {
				timeLimits.push(languageName);
			} else {
				timeLimit.set(time, timeLimits = [languageName]);
			}

			if (memoryLimits) {
				memoryLimits.push(languageName);
			} else {
				memoryLimit.set(memory, memoryLimits = [languageName]);
			}
		}


		const toString = (map, keyProcess) => {
			let str = "";

			if (!map || !map.size) return str;


			for(let entry of map.entries()) {
				let key = entry[0];
				let array = entry[1];

				let temp = "";

				for (let i = 0; i < array.length; i++) {
					let item = array[i];

					if (i) temp += "/";

					temp += item;
				}


				if (str) str += "&nbsp;&nbsp;&nbsp;&nbsp;";

				str += temp + " " + keyProcess(key);
			}
			return str;
		};


		problem.timeLimit = toString(timeLimit, key => `${key / 1000}s`);
		problem.memoryLimit = toString(memoryLimit, key => `${key / 1024}MB`);

		return problem;
	}


	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 


	static get(id) {
		return ApiDao.findOne({
			table: "view_problem",
			columns: [
				"id", "name", "description", 
				"input", "output", "limits",
				"sampleInput", "sampleOutput", "sampleCode", 
				"tags", "hint", "source",
				"accepted", "submit"
			],
			conditions: {
				id: id,
			}
		});
	}

	static getPage(pageNumber, pageSize, conditions) {
		let obj = {
			enable: 1,
		};

		Object.assign(obj, removeInvalidCondition(conditions));

		return ApiDao.findPage({
			table: "view_problem",
			columns: ["id", "name", "tags", "accepted", "submit"],
			conditions: obj,
			page: {
				number: pageNumber,
				size: pageSize,
			},
		});
	}


	static submit(id, lang, code) {
		const data = {
			id: id,
			language: lang,
			code: code,
		};
		console.log("[problem] submit: ", data);

		return ApiDao.post(Router.api.problem.judge, data);
	}


	static getStatus(runId, tryTimes=16, interval=1000) {
		if (!runId || tryTimes < 1 || interval < 200) return;

		return new Promise((resolve, reject) => {
			let counter = 0;
			let url = Router.api.judge.status(runId);

			const temp = setInterval(() => {
				counter++;

				ApiDao.getJson(url).then(result => {
					if (true === result.complete) {
						// console.log(counter + ': ', result);

						clearInterval(temp);

						resolve(result);
					}
				}).catch(error => reject(error));


				if (counter > tryTimes) {
					clearInterval(temp);

					reject("请稍后重试!")
				}
			}, interval);
		});
	}
}


export class ContestService {

	static get(id) {
		return ApiDao.findOne({
			table: "view_contest",
			conditions: {
				id: id,
			},
		});
	}

	static getPage(number, size, conditions) {
		let obj = {};

		Object.assign(obj, removeInvalidCondition(conditions));

		return ApiDao.findPage({
			table: "view_contest",
			conditions: obj,
			page: {
				number: number,
				size: size,
			},
		});
	}




	static getRank(id) {
		return ApiDao.findList({
			table: "view_contest_rank",
			columns: ["ranking", "userId", "username", "usernick", "solved", "penalty", "problems"],
			conditions: {
				contestId: id,
			},
		});
	}

	static getStatus(contestId, number, size) {
		return ApiDao.findPage({
			table: "view_contest_solution",
			conditions: {
				contestId: contestId,
			},
			page: {
				number: number,
				size: size,
			},
		});
	}



	static getProblems(id, columns=['id', 'identifier', 'name', 'source', 'accepted', 'submit']) {
		return ApiDao.findList({
			table: "view_contest_problem",
			columns: columns,
			conditions: {
				contestId: id,
			},
		});
	}

	static getSolvedProblems(id) {
		return ApiDao.findOne({
			table: "view_user_contest_problem_statuses",
			columns: ["solvedList"],
			conditions: {
				contestId: id,
			},
		});
	}
}



export class ContestProblemService {

	static get(contestId, id) {
		return ApiDao.findOne({
			table: "view_contest_problem",
			columns: ["*"],
			conditions: {
				id: id,
				contestId: contestId,
			},
		});
	}




	static submit(contestId, identifier, lang, code) {
		console.log("[submit] ", contestId , "-", identifier, "(", lang, ": ", code, ")");

		return ApiDao.post(Router.api.contest.problem.judge, {
			contestId: contestId,
			identifier: identifier,
			lang: lang,
			code: code,
		});
	}
}


export class ContestStatusService {

	static #statusMapping = {
		0: {
			name: "status.pending",
			attr: "signupBeginTime",
			color: "#0069d9",
		},
		1: {
			name: "status.signup",
			attr: "signupEndTime",
			color: "#0069d9",
		},
		2: {
			name: "status.scheduled",
			attr: "beginTime",
			color: "#0069d9",
		},
		3: {
			name: "status.running",
			attr: "endTime",
			color: "#28A745",
		},
		4: {
			name: "status.ended",
			attr: "endTime",
			color: "red",
		},
	};

	static processes(items) {
		for (let item of items) {
			this.processWithoutFreeze(item);
		}
		return Object.freeze(items);
	}

	static process(item) {
		return Object.freeze(this.processWithoutFreeze(item));
	}

	static processWithoutFreeze(item) {
		let status = this.#statusMapping[item.status || 0];

		item.statusName = status ? status.name : '';

		if (status) {
			item.statusColor = status.color;
			item.time = item[status.attr];
		}

		return item;
	}

	static processProgress(items) {
		items.forEach(item => {
			if (item.subtask && item.finshed) {
				let subtask = item.subtask;
				let finshed = item.finshed;
				let width = (finshed / subtask) * 100;

				item.progressText = `${finshed} / ${subtask}`;

				item.progressStyle = `width: ${width}%;`;

				item.progressClass = (finshed < subtask)
						? "bgc-info" : "bgc-success";
			}
		});
	}


	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


	static getPage(contestId, number, size, conditions) {
		let obj = {
			contestId: contestId
		};

		Object.assign(obj, removeInvalidCondition(conditions));


		return ApiDao.findPage({
			table: "view_contest_solution",
			conditions: obj,
			page: {
				number: number,
				size: size,
			},
		});
	}
}


export class RankService {

	static getPage(pageNumber, pageSize) {
		return ApiDao.findPage({
			table: "view_rank",
			page: {
				number: pageNumber,
				size: pageSize,
			},
		});
	}

}


export class PasteService {


	static getSupportLang() {
		return ApiDao.findList({
			table: "language",
		});
	}


	static get(id) {
		return ApiDao.findOne({
			table: "view_paste",
			conditions: {
				id: id,
			},
		});
	}



	static submit(lang, content) {
		return ApiDao.postWithJson(Router.api.paste.add, {
			table: "paste",
			attrs: {
				language: lang,
				content: content,
			},
		});
	}
}


export class UserService {

	static regist(account, passwd, vcode) {
		return new Promise((resolve, reject) => {
			let message;

			if ((message = this.checkAccount(account) 
						|| this.checkPassword(passwd) 
						|| this.checkVcode(vcode))) {
				reject(message);
				return;
			}


			ApiDao.post(Router.api.user.regist, {
				username: account,
				password: passwd,
				vcode: vcode,
			}).then(result => {
				resolve(result);
			}).catch(error => {
				reject(error);
			});
		});
	}

	static changePassword(oldPassword, newPassword) {
		return new Promise((resolve, reject) => {
			if (oldPassword === newPassword) {
				reject("新密码不能与原密码相同!");
				return;
			}


			let message;

			if ((message = this.checkPassword(oldPassword)
						|| this.checkPassword(newPassword))) {
				reject(message);
				return;
			}


			ApiDao.post(Router.api.user.changePasswd, {
				oldPassword: oldPassword,
				newPassword: newPassword,
			}).then(result => {
				resolve(result);
			}).catch(error => reject("修改密码失败"));
		}).then(() => this.clearCache());;
	}

	static resetPassword(account, passwd, vcode) {
		return new Promise((resolve, reject) => {
			let message;

			if ((message = this.checkEmail(account)
						|| this.checkPassword(passwd)
						|| this.checkVcode(vcode))) {
				reject(message);
				return;
			}


			ApiDao.post(Router.api.user.resetPasswd, {
				email: account,
				password: passwd,
				vcode: vcode,
			}).then(result => {
				resolve(result);
			}).catch(error => {
				reject(error);
			});
		}).then(() => this.clearCache());;
	}

	static sendResetPasswordVcode(email) {
		return new Promise((resolve, reject) => {
			let message;

			if (message = this.checkEmail(email)) {
				reject(message);
				return;
			}


			ApiDao.post(Router.api.user.resetPasswdCaptcha(), {
				email: email,
			}).then(result => {
				resolve(result);
			}).catch(error => {
				reject(error);
			});
		});
	}

	static sendBindEmailVcode() {
		return ApiDao.post(Router.api.user.email.bindCaptcha());
	}

	static sendUnbindEmailVcode() {
		return ApiDao.post(Router.api.user.email.unbindCaptcha());
	}


	static changeEmail(email) {
		return new Promise((resolve, reject) => {
			if (email) {
				let message = this.checkEmail(email);

				if (message) {
					reject(message);
					return;
				}
			}


			ApiDao.post(Router.api.user.email.change, {
				email: email,
			}).then(result => {
				resolve(result);
			}).catch(error => {
				reject(error);
			});
		}).then(() => this.clearCache());;
	}

	static bind(email, vcode) {
		return new Promise((resolve, reject) => {
			let message;

			if ((message = this.checkEmail(email)
						|| this.checkVcode(vcode))) {
				reject(message);
				return;
			}


			ApiDao.post(Router.api.user.email.bind, {
				email: email,
				vcode: vcode,
			}).then(result => {
				resolve(result);
			}).catch(error => {
				reject(error);
			});
		}).then(() => this.clearCache());;
	}

	static unbind(email, vcode) {
		return new Promise((resolve, reject) => {
			let message;

			if ((message = this.checkEmail(email)
						|| this.checkVcode(vcode))) {
				reject(message);
				return;
			}


			ApiDao.post(Router.api.user.email.unbind, {
				email: email,
				vcode: vcode,
			}).then(result => {
				resolve(result);
			}).catch(error => {
				reject(error);
			});
		}).then(() => this.clearCache());;
	}


	static login(account, passwd, vcode) {
		return new Promise((resolve, reject) => {
			let message;

			if ((message = this.checkAccount(account) 
						|| this.checkPassword(passwd) 
						|| this.checkVcode(vcode))) {
				reject(message);
				return;
			}



			ApiDao.post(Router.api.user.login, {
				username: account,
				password: passwd,
				vcode: vcode,
			}).then(result => {
				resolve(result);
			}).catch(error => {
				console.log('err: ', error);
				reject(error);
			});
		});
	}


	static checkAccount(account) {
		if ('string' !== typeof(account)
				|| account.length < 2 
				|| account.length > 18) {
			return "账号格式不正确"
		}
		return null;
	}

	static checkEmail(account) {
		const patten = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;

		if ('string' !== typeof(account) || !patten.test(account)) {
			return "邮箱格式不正确"
		}
		return null;
	}

	static checkPassword(passwd) {
		if ('string' !== typeof(passwd)) {
			return "密码格式不正确"
		}


		const minLength = 6, maxLength = 18;

		if (passwd.length < minLength || passwd.length > maxLength) {
			return `密码长度应在${minLength}-${maxLength}之间`
		}
		return null;
	}

	static checkVcode(vcode) {
		const patten = /^[a-zA-Z0-9]{4,6}$/;

		if ('string' !== typeof(vcode) || !patten.test(vcode)) {
			return "验证码格式不正确";
		}
		return null;
	}




	static #cache = {
		USER_KEY: "toj-user-cache",

		get() {
			let temp = sessionStorage.getItem(this.USER_KEY);

			if ("string" !== typeof(temp)) {
				this.remove();

				return null;
			}


			let user = null;

			try {
				user = JSON.parse(temp);
			} catch(e) {
				this.remove();
			}

			return user;
		},
		put(user) {
			if (!user) return;

			sessionStorage.setItem(this.USER_KEY, JSON.stringify(user));
		},
		remove() {
			sessionStorage.removeItem(this.USER_KEY);
		}
	};


	static info(enableCache=true) {
		return new Promise((resolve, reject) => {
			let cachedUser = this.current();

			if (cachedUser) {
				resolve(cachedUser);
				return;
			}

			ApiDao.postWithJson(Router.api.user.profile, [
				'id', 'name', 'nick', 'email', 'emailVerified', 'phone', 'ext'
			]).then(result => {
				const user = result;

				user.ext = JSON.parse(user.ext);

				resolve(user);
			}).catch(error => reject(error));
		});
	}

	static solvedList() {
		return ApiDao.postWithJson(Router.api.user.profile, ['solvedList'])
		.then(result => {
			return JSON.parse(result.solvedList);
		});
	}

	static modifyProfile(profile) {
		return ApiDao.post(Router.api.user.update, profile)
		.then(() => this.clearCache());
	}

	static judgeInfo(userId) {
		return new Promise((resolve, reject) => {
			ApiDao.getJson(Router.api.user.judgeInfo(userId))
			.then(result => {
				result.solvedList = JSON.parse(result.solvedList);
				result.unsolvedList = JSON.parse(result.unsolvedList);
				resolve(result);
			}).catch(error => reject(error));
		});
	}


	static checkLogin() {
		return new Promise((resolve, reject) => {
			ApiDao.getJson(Router.api.user.isLogin).then(result => {
				resolve();
			}).catch(error => {
				this.#cache.remove();

				reject(error);
			});
		});
	}


	static logout() {
		return ApiDao.getJson(Router.api.user.logout).then(() => {
			this.#cache.remove();
		});
	}

	static clearCache = () => this.#cache.remove();

	static current = () => this.#cache.get();
}



export class SolutionService {

	static processes(solutions, langMapping) {
		for (let item of solutions) {
			item.languageName = langMapping[item.language];
		}
	}


	static process(solution, langMapping) {
		solution.languageName = langMapping[solution.language];
	}


	static get(id, contest=false) {
		return ApiDao.findOne({
			table: contest ? "view_contest_solution" : "view_solution",
			conditions: {
				id: id,
			},
		});
	}
}


export class GroupService {

	static getUrl(id) {
		return "/group/" + id;
	}

	static getPageUrl(number, size) {
		return "/groups?" + parameterizePns(number, size);
	}


	static getAllUrl(number, size) {
		return "/groups?" + parameterizePns(number, size);
	}

	static getMyCreationUrl(number, size) {
		return "/groups/creation?" + parameterizePns(number, size);
	}

	static getMyJoinedUrl(number, size) {
		return "/group/joined?" + parameterizePns(number, size);
	}





	static getDataUrl(groupId) {
		return `/mock/group/${groupId}.json`;
	}


	static getNotificationsDataUrl(groupId) {
		return `/mock/group/notifications/${groupId}.json`;
	}

	static getMembersDataUrl(groupId) {
		return `/mock/group/members/${groupId}.json`;
	}

	static getTasksDataUrl(groupId) {
		return `/api/group/tasks?id=${groupId}`;
	}


	static getAllDataUrl(number, size) {
		return "/mock/groups/all/" + contactPns(number, size) + ".json";
	}

	static getMyCreationDataUrl(number, size) {
		return "/mock/groups/creation/" + UserService.name() + "/"
				+ contactPns(number, size) + ".json";
	}

	static getMyJoinedDataUrl(number, size) {
		return "/mock/groups/joined/" + UserService.name() + "/" 
				+ contactPns(number, size) + ".json";
	}




	static get(groupId) {
		return ApiDao.findOne({
			table: "view_group",
			conditions: {
				id: groupId
			},
		});
	}


	static getNotifications(groupId) {
		return ApiDao.findList({
			table: "group_notification",
			conditions: {
				groupId: groupId,
				enable: 1,
			},
		});
	}

	static getMembers(groupId) {
		return ApiDao.findList({
			table: "view_group_member",
			conditions: {
				groupId: groupId
			},
		});
	}

	static getTasks(groupId) {
		return ApiDao.getJson(this.getTasksDataUrl(groupId));
	}


	static getAll(number, size) {
		return ApiDao.findPage({
			table: "view_group",
			conditions: {
				enable: 1,
			},
			page: {
				number: number, 
				size: size, 
			}
		});
	}

	static getMyCreation(number, size) {
		return ApiDao.findPage({
			table: "group",
			page: {
				number: number, 
				size: size, 
			}
		});
	}

	static getMyJoined(number, size) {
		return ApiDao.findPage({
			table: "view_joined_group",
			page: {
				number: number, 
				size: size, 
			}
		});
	}




	static joinUrl(groupId, groupName) {
		return `/group/${groupId}/join`
				+ "?name=" + groupName;
	}


	static join(groupId, password) {
		// todo
		return new Promise((resolve, reject) => {
			reject("暂不支持该功能...");
		});
	}
}



// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 



export class UniqueSelector {
	#object;
	#current;

	constructor(object={}, current=null) {
		this.#object = object;
		this.#current = current;
	}


	select() {
		return this.#current;
	}

	value() {
		return this.#object[this.#current];
	}

	avaliable(key) {
		return this.#object.hasOwnProperty(key);
	}

	reselect(key) {
		let temp = this.value();

		this.#current = key;

		return temp;
	}



	/**
	 * 通过list构造
	 * @param  {Array}  list		对象数组
	 * @param  {String} keyAttr 	作为key的字段名
	 * @return {UniqueSelector}	 US || null
	 */
	static byList(list=[], keyAttr="id", current) {
		if (!list.length || !keyAttr) {
			return new UniqueSelector({}, current);
		}


		let obj = {};

		for (let item of list) {
			obj[item[keyAttr]] = item;
		}

		return new UniqueSelector(obj, current);
	}
}


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

